/*
 *
 * Copyright (C) 2012 Walter Lütgenau
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 */
package offset.nodes.server.core.services;

import java.io.ByteArrayOutputStream;
import java.io.Serializable;
import java.util.Locale;
import javax.jcr.LoginException;
import javax.jcr.Node;
import javax.jcr.NodeIterator;
import javax.jcr.Repository;
import javax.jcr.RepositoryException;
import javax.jcr.Session;
import javax.jcr.SimpleCredentials;
import offset.nodes.Constants;
import offset.nodes.server.error.model.ExceptionId;
import offset.nodes.server.error.model.LoggedException;
import offset.nodes.client.dialog.login.model.Login;
import offset.nodes.client.model.ServerRequest;
import offset.nodes.client.model.ServerResponse;
import offset.nodes.client.virtual.model.jcr.SimpleNode;
import offset.nodes.client.virtual.model.jcr.NodeWriter;
import offset.nodes.client.virtual.model.jcr.PrintHandler;
import offset.nodes.client.dialog.ac.model.GetAccessControl;
import offset.nodes.client.dialog.ac.model.SetAccessControl;
import offset.nodes.server.controller.AttributeConstants;
import offset.nodes.server.controller.RepositoryPlugIn;
import offset.nodes.server.model.RepositoryModel;
import offset.nodes.server.model.ServerNamespaceRegistry;
import offset.nodes.server.model.UserInfo;
import offset.nodes.server.servlet.AbstractRepositoryService;
import offset.nodes.server.servlet.Service;
import offset.nodes.server.servlet.ServiceContainer;
import offset.nodes.server.servlet.ServiceMapping;
import org.apache.struts.action.ActionForward;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 *
 * @author Walter Lütgenau
 */
public class AccessControlServices extends AbstractRepositoryService implements ServiceContainer {

    protected ServerNamespaceRegistry namespaces = new ServerNamespaceRegistry();
    static Logger logger = LoggerFactory.getLogger(AccessControlServices.class);

    /** Creates a new instance of DialogServices */
    public AccessControlServices() {
    }

    public ServiceMapping[] getServiceMappings() {
        return new ServiceMapping[]{
                    new ServiceMapping(new Login.Request().getId(), new Service() {

                public Serializable service(ServerRequest request) throws Exception {
                    return login((Login.Request) request);
                }
            }, this),
                    new ServiceMapping(new SetAccessControl.Request().getId(), new Service() {

                public Serializable service(ServerRequest request) throws Exception {
                    return setAccessControl((SetAccessControl.Request) request);
                }
            }, this),
                    new ServiceMapping(new GetAccessControl.Request().getId(), new Service() {

                public Serializable service(ServerRequest request) throws Exception {
                    return getAccessControl((GetAccessControl.Request) request);
                }
            }, this)
                };
    }

    protected Login.Response login(Login.Request request) throws RepositoryException {
        Login.Response response = new Login.Response(ServerResponse.RESULT_SUCCESS);

        Repository repository = RepositoryPlugIn.getRepository();

        Session session = null;
        try {
            session = repository.login(
                    new SimpleCredentials(request.getUserName(), request.getPassword().toCharArray()));
        } catch (LoginException le) {
            throw new LoggedException(ExceptionId.GENERAL_LOGIN_FAILED);
        }

        // todo: find user in repository, authentify user
        UserInfo user = new UserInfo();
        user.setLocale(Locale.getDefault());

        getRequest().getSession().setAttribute(AttributeConstants.ATT_REPOSITORY_SESSION, session);
        getRequest().getSession().setAttribute(AttributeConstants.ATT_USER_INFO, user);

        ActionForward forward = (ActionForward) getRequest().getSession().getAttribute(AttributeConstants.ATT_LOGIN_FORWARD);
        if (forward != null) {
            String path = forward.getPath();
            if (path.startsWith(Constants.PATH_SEPARATOR))
                path = path.substring(1);
            response.setForward(path);
        }

        return response;
    }

    protected String nodeToString(Node node) throws Exception {
        if (node == null)
            return null;

        ByteArrayOutputStream out = new ByteArrayOutputStream();
        NodeWriter writer = new NodeWriter(node, new PrintHandler(out));
        writer.write();

        return out.toString();
    }

    protected Node getInheritedAccessControl(Node node) throws Exception {
        if (node.getName().equals(Constants.JCR_ROOT))
            return null;

        Node parent = node;
        SimpleNode accessControl = null;
        do {
            parent = parent.getParent();
            String name = parent.getName();

            if (parent.hasNode(Constants.TYPENAME_ACCESS_CONTROL))
                if (accessControl == null)
                    accessControl = new SimpleNode(parent.getNode(Constants.TYPENAME_ACCESS_CONTROL));
                else {
                    NodeIterator ni = parent.getNode(Constants.TYPENAME_ACCESS_CONTROL).getNodes();
                    while (ni.hasNext()) {
                        Node child = ni.nextNode();

                        accessControl.addNode(new SimpleNode(child));
                    }
                }
        } while (parent.getName().length() > 0);

        return accessControl;
    }

    public GetAccessControl.Response getAccessControl(GetAccessControl.Request request) throws Exception {
        GetAccessControl.Response response = new GetAccessControl.Response(ServerResponse.RESULT_SUCCESS);

        Node accessControl = getNode(getSession(request).getRootNode(), request.getPath());
        Node parent = accessControl.getParent();

        response.setNodeAccessControl(nodeToString(accessControl));
        response.setInheritedAccessControl(nodeToString(getInheritedAccessControl(parent)));

        return response;
    }

    public SetAccessControl.Response setAccessControl(SetAccessControl.Request request) throws RepositoryException {
        SetAccessControl.Response response = new SetAccessControl.Response(ServerResponse.RESULT_SUCCESS);

        Session session = getSession(request);
        Node node = getNode(session.getRootNode(), request.getPath());
        if (!node.isNodeType(Constants.TYPENAME_ACCESS_CONTROL) && !node.isNodeType(Constants.TYPENAME_ACCESS_CONTROLLER))
            node.addMixin(Constants.TYPENAME_ACCESS_CONTROLLER);

        XMLToJCRUpdater updater = new XMLToJCRUpdater(session, node);
        updater.update(request.getNodeAccessControl());

        session.save();

        return response;
    }

    /* interface
     ****************************************************************************************************
     * internal */
    protected byte[] createSampleHtml(Node schema) throws RepositoryException {
        return ("<body>"
                + "<div  n-schema=\"" + schema.getPath() + "\"></div>"
                + "</body>").getBytes();
    }

    protected byte[] createEmptyStylesheet() {
        return ("<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
                + "<xsl:stylesheet version=\"1.0\" xmlns:xsl=\"http://www.w3.org/1999/XSL/Transform\">"
                + "<xsl:import href=\"template-basic.xsl\"/>"
                + "</xsl:stylesheet>").getBytes();
    }

    /**
     * Return a node with a relative path to the root. Create path nodes, if necessory
     * 
     * @param root
     * @param path
     * @return
     * @throws javax.jcr.RepositoryException
     */
    protected Node getNode(Node root, String path) throws RepositoryException {
        RepositoryModel repositoryModel = new RepositoryModel(root.getSession());
        return repositoryModel.getNode(root, path);
    }

    protected byte[] extractHtmlBody(byte[] contentBuffer) {
        String content = new String(contentBuffer);
        content = content.substring(content.indexOf("<body>"));
        content = content.substring(0, content.indexOf("</body>") + "</body>".length());

        return content.getBytes();
    }

    protected String getChild(String parent, String path) {
        if (path.startsWith(parent))
            path = path.substring(parent.length());
        if (path.charAt(0) == '/')
            path = path.substring(1);
        int index = -1;
        if ((index = path.indexOf("/")) >= 0)
            path = path.substring(0, index);

        return path;
    }

    protected boolean hasChildren(String parent, String path) {
        if (path.startsWith(parent))
            path = path.substring(parent.length());

        int index = -1;
        if ((index = path.indexOf("/")) >= 0)
            path = path.substring(index + 1);
        else
            return false;

        return path.indexOf("/") >= 0;
    }

    protected Node getNode(Session session, String path) throws RepositoryException {
        Node root = session.getRootNode();
        if (path.startsWith("/"))
            path = path.substring(1);

        if (!root.hasNode(path))
            return null;
        return root.getNode(path);
    }

    /**
     * Can this node have children? Look into the node type definition.
     *
     * @param node
     * @return the result
     */
    protected boolean canHaveChildren(Node node) throws RepositoryException {
        return node.getPrimaryNodeType().getChildNodeDefinitions().length > 0;
    }
}
